﻿using System;
using System.Collections;
using System.Reflection;

namespace Datagility.Shpt.Util.SettingsEntities
{
    public class SettingsStore
    {
        /// <summary>
        /// Takes a generic collection of names and values and uses these to
        /// populate a class whose properties match the supplied names. Commonly
        /// used when reading application settings from a SharePoint list.
        /// </summary>
        /// <param name="settingsEntity">An instance of a generic collection containing names and values. 
        /// Ususally a Microsoft.SharePoint.Linq.EntityList as created by SPMetal, but can actually
        ///  be any class that implements IEnumerable.</param>
        /// <param name="settingsList">An instance of the class whose properties we wish 
        /// to populate.</param>
        /// <param name="settingFieldName">The name of the property in the settingsEntity object 
        /// that contains the names e.g. 'title'</param>
        /// <param name="valueFieldName">The name of the property in the settingsEntity object 
        /// that contains the values e.g. 'settingValue'</param>
        /// <returns>The populated Entity class instance</returns>
        public static object Read(object settingsEntity, object settingsList,
            string settingFieldName, string valueFieldName)
        {
            DoReadWrite(settingsEntity, settingsList, settingFieldName,
                valueFieldName, true);

            return settingsEntity;
        }

        /// <summary>
        /// Takes a generic collection of names and values and  populates them from a class 
        /// whose properties match the supplied names. Commonly
        /// used when writing application settings to a SharePoint list.
        /// </summary>
        /// <param name="settingsEntity">An instance of a generic collection containing names and values. 
        /// Ususally a Microsoft.SharePoint.Linq.EntityList as created by SPMetal, but can actually
        ///  be any class that implements IEnumerable.</param>
        /// <param name="settingsList">An instance of the class whose properties we wish 
        /// to read.</param>
        /// <param name="settingFieldName">The name of the property in the settingsEntity object 
        /// that contains the names e.g. 'title'</param>
        /// <param name="valueFieldName">The name of the property in the settingsEntity object 
        /// that contains the values e.g. 'settingValue'</param>
        public static void Write(object settingsEntity, object settingsList,
            string settingFieldName, string valueFieldName)
        {
            DoReadWrite(settingsEntity, settingsList, settingFieldName,
                valueFieldName, false);
        }

        private static void DoReadWrite(object settingsEntity, object settingsList,
            string settingFieldName, string valueFieldName, bool isRead)
        {
            //Check the arguments
            if (settingsEntity == null)
                throw new ArgumentNullException("settingsEntity");

            if (settingsList == null)
                throw new ArgumentNullException("settingsList");

            if (String.IsNullOrEmpty(settingFieldName))
                throw new ArgumentNullException("settingFieldName", 
                    "Argument cannot be null or empty");

            if (String.IsNullOrEmpty(valueFieldName))
                throw new ArgumentNullException("valueFieldName",
                    "Argument cannot be null or empty");

            //Get the enumerator for the object that represents the settings read from
            // the SharePoint list (we assume Microsoft.SharePoint.Linq.EntityList) settingsList
            IEnumerable enumtor = settingsList as IEnumerable;

            if (enumtor != null)
            {
                //Try and match the names of the properties of the entities class with the
                // appropirate values in the SharePoint list
                PropertyInfo[] entityProps = settingsEntity.GetType().GetProperties();

                foreach (object sett in enumtor)
                {
                    //Get the Name/Value properties from the list class based on the supplied
                    // parameters
                    Type settType = sett.GetType();
                    PropertyInfo settTitle = settType.GetProperty(settingFieldName);
                    if(settTitle == null)
                        throw new FieldAccessException(String.Format("Could not find the Property {0}", 
                            settingFieldName));

                    PropertyInfo settValue = settType.GetProperty(valueFieldName);
                    if (settValue == null)
                        throw new FieldAccessException(String.Format("Could not find the Property {0}",
                            valueFieldName));

                    //Get the name of this particular setting as defined in the SharePoint list
                    MethodInfo settFieldInf = settTitle.GetGetMethod();
                    string settField = Convert.ToString(settFieldInf.Invoke(sett, null));

                    //Find the appropriate property in the Entity class
                    for (int i = 0; i < entityProps.Length; i++)
                    {
                        if (settField == entityProps[i].Name)
                        {
                            if (isRead)
                            {
                                ReadValue(sett, settValue, settingsEntity, entityProps[i]);
                            }
                            else
                            {
                                WriteValue(sett, settValue, settingsEntity, entityProps[i]);
                            }

                            break;
                        }
                    }
                }
            }
            else
            {
                throw new ArgumentException("Settings list object must implement IEnumerable");
            }
        }

        private static void ReadValue(object nvpObject , PropertyInfo nvpValueProp,
            object entityObject, PropertyInfo entityProp)
        {
            //Read the value from the NVP object...
            MethodInfo settValInf = nvpValueProp.GetGetMethod();
            object settVal = settValInf.Invoke(nvpObject, null);

            //... and write it to the Entity class property
            MethodInfo entInf = entityProp.GetSetMethod();
            ParameterInfo[] pInf = entInf.GetParameters();
            Type pType = pInf[0].ParameterType;

            object finalVal = null;
            if (settVal != null)
            {
                finalVal = Convert.ChangeType(settVal, pType);
                entInf.Invoke(entityObject, new object[] { finalVal });
            }
        }

        private static void WriteValue(object nvpObject, PropertyInfo nvpValueProp,
            object entityObject, PropertyInfo entityProp)
        {
            //Read the value from the Entity object...
            MethodInfo settValInf = entityProp.GetGetMethod();
            object settVal = settValInf.Invoke(entityObject, null);

            //... and write it to the NVP class property
            MethodInfo nvpInf = nvpValueProp.GetSetMethod();
            ParameterInfo[] pInf = nvpInf.GetParameters();
            Type pType = pInf[0].ParameterType;

            object finalVal = Convert.ChangeType(settVal, pType);

            nvpInf.Invoke(nvpObject, new object[] { finalVal });
        }
    }
}
